Skip to content

feat: MCP companions for skill v8 (pin bump + SET-LEVEL + Inv #1.a allowlist) - #480

Merged
szhygulin merged 1 commit into
mainfrom
feat/skill-v8-mcp-bundle
Apr 28, 2026
Merged

feat: MCP companions for skill v8 (pin bump + SET-LEVEL + Inv #1.a allowlist)#480
szhygulin merged 1 commit into
mainfrom
feat/skill-v8-mcp-bundle

Conversation

@szhygulin

Copy link
Copy Markdown
Collaborator

Summary

Coordinated MCP-side companion for vaultpilot-security-skill#19 (skill v0.6.0 — adversarial-smoke-test batch). DRAFT until the skill PR merges.

Three bundled changes:

1. Pin bump (src/diagnostics/skill-pin-drift.ts)

Constant Before After
EXPECTED_SKILL_SHA256 b70085df…0799 01d8d68d…1414
EXPECTED_SKILL_SENTINEL_B _v7_ _v8_
EXPECTED_SKILL_SENTINEL_C 8e252312c08c415b 4aac027a9df315a9

2. [SET-LEVEL ENUMERATION] block on get_token_allowances (Q1.B, closes #450)

New src/security/set-level-enumeration.ts renders a verbatim markdown row table the agent surfaces under skill v8's Invariant #14. tokenAllowancesHandler wrapper appends the block to every response. Missing block on a real get_token_allowances response = Invariant #4 compromise signal.

3. Inv #1.a canonical-contract allowlist mirror (Q7.B, closes #461)

New src/security/canonical-dispatch.ts exposes assertCanonicalDispatchTarget(toolName, chain, to). The allowlist is derived from src/config/contracts.ts — single source of truth, no hand-mirrored table. Regression test asserts every allowlist entry resolves to a real CONTRACTS address.

Coverage: prepare_aave_*, prepare_compound_*, prepare_lido_stake/_unstake, prepare_morpho_*, prepare_uniswap_swap, prepare_uniswap_v3_*, prepare_eigenlayer_deposit.

Per-handler wiring deferred to a follow-up PR — different prepare flows have different tx-chain shapes (approve+action multi-leg vs single-tx vs other). Per the smallest-solution discipline in CLAUDE.md, this PR ships the helper module + regression test; wiring is a separate per-flow review.

What's NOT included (intentional)

Q3.C (CONTACT-CHAIN MISMATCH on preview_send) — the contacts schema is per-chain-family (btc | evm | solana | tron), not per-EVM-chain, so the check has no schema-level signal to fire on for the smoke-test scenario (Carol-on-Arbitrum sent on Ethereum). Filed as a follow-up: "extend contact metadata with per-EVM-chain tag." Skill-side Inv #2.5 positive-chain-naming remains the load-bearing defense.

⚠️ Merge ordering

This PR is DRAFT. Merge skill#19 first; otherwise every signing flow halts with vaultpilot-preflight skill integrity check FAILED while master shows v7 and the pin expects v8.

Test plan

  • Full vitest suite: 2151/2151 pass
  • Typecheck clean (tsc --noEmit)
  • canonical-dispatch.test.ts: 11 cases — drift detection, family-prefix matching, most-specific-tool wins, multi-Comet allowlist, Lido Ethereum-only, Uniswap router/NPM split
  • set-level-enumeration.test.ts: 3 cases — header + table, empty list, truncation warning
  • skill-pin-drift.test.ts: 17 cases against new constants
  • After skill#19 merges: end-to-end signing flow passes Step 0 integrity check

🤖 Generated with Claude Code

… mirror

Coordinated MCP-side companion for vaultpilot-security-skill#19 (skill
v0.6.0 — adversarial-smoke-test batch).

Pin bump (skill-pin-drift.ts):
- EXPECTED_SKILL_SHA256: b70085df…0799 → 01d8d68d…1414
- EXPECTED_SKILL_SENTINEL_B: _v7_ → _v8_
- EXPECTED_SKILL_SENTINEL_C: 8e252312c08c415b → 4aac027a9df315a9

[SET-LEVEL ENUMERATION] block on get_token_allowances (Q1.B, #450):
- New src/security/set-level-enumeration.ts renders verbatim row table
- tokenAllowancesHandler wrapper appends the block to every response
- Skill v8 §14 mandates the block; missing block is an Inv #4
  compromise signal (the rogue agent silently filtered the row set)

Inv #1.a outer dispatch-target allowlist mirror (Q7.B, #461):
- New src/security/canonical-dispatch.ts derives the (toolFamily,
  chain) → expected `to` map from src/config/contracts.ts (single
  source of truth, no hand-mirrored table)
- Tool families covered: prepare_aave_*, prepare_compound_*,
  prepare_lido_stake/_unstake, prepare_morpho_*, prepare_uniswap_swap,
  prepare_uniswap_v3_*, prepare_eigenlayer_deposit
- Regression test asserts every allowlist address resolves to a real
  CONTRACTS entry — the lookup cannot silently fall out of sync
- Per-handler wiring (calling assertCanonicalDispatchTarget on each
  prepare_* result) is deferred to a follow-up PR — different prepare
  flows have different tx-chain shapes (approve+action multi-leg,
  single-tx, etc.) so wiring needs per-flow review. Skill-side check
  remains the load-bearing defense for rogue-MCP threat; this
  module's job is to exist as the MCP-side mirror per Q7.B and to
  fail-fast in unit tests if the table drifts.

Q3.C (CONTACT-CHAIN MISMATCH on preview_send) intentionally NOT
included — the contacts schema is per-chain-family (btc/evm/solana/
tron), not per-EVM-chain, so the check has no schema-level signal to
fire on for the smoke-test scenario. Filed as a follow-up: extend
contact metadata with per-EVM-chain tag, then add the warning. The
skill-side Inv #2.5 positive-chain-naming rule remains the
load-bearing defense.

MUST land AFTER skill v8 PR (vaultpilot-security-skill#19) merges.

Closes vaultpilot-mcp#450, #461 (skill+MCP load-bearing defense for
both is now in place).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@szhygulin
szhygulin marked this pull request as ready for review April 28, 2026 05:46
@szhygulin
szhygulin merged commit 7b48b5e into main Apr 28, 2026
4 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 28, 2026
@szhygulin
szhygulin deleted the feat/skill-v8-mcp-bundle branch May 19, 2026 04:26
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[security] Add Invariant #1.a — outer-to canonical-contract allowlist [security] Reverse-revoke / set-level lie defeats bytes-level preflight

1 participant